﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace Paxos
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
    public class Proposer : IPaxosProproser
    {
        private ReplicaStateMachine _replica;

        public Proposer(ReplicaStateMachine replica)
        {
            _replica = replica;
        }

        public ServerView GetView()
        {
            return _replica._view;
        }

        public IssueResult IssueCommand(PaxosCommand command)
        {
            Console.WriteLine("Issue command {0}", command);
            SelfAppointProposal selfAppoint= new SelfAppointProposal() 
            { 
                Ballot = _replica._localAcceptor._lastPromised.Ballot,
                ID = _replica._localAcceptor._lastPromised.ID + 1,
                DecreeGuid = _replica._id
            };             

            int OKCount = 0;
            List<ConcreteProposal> proposals = new List<ConcreteProposal>();

            // prepare
            Console.WriteLine("Please prepare {0}", selfAppoint);
            foreach (IPaxosAcceptor acceptor in _replica._acceptors)
            {
                PromiseResult ret;
                try
                {
                    ret = acceptor.Promise(selfAppoint);
                    Console.WriteLine("Get prepare-ret {0}", ret);
                    if (ret.Promised.Ballot > _replica._localAcceptor._lastPromised.Ballot)
                    {
                        return IssueResult.OutOfDate;
                    }
                }
                catch (CommunicationObjectFaultedException e)
                {
                    _replica._needToCacheEndpoint = true;
                    ret = null;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Caught exception {0}: {1}", e.GetType().Name, e.Message);
                    ret = null;
                }

                if (ret != null && ret.Answer)
                {
                    if (ret.Accepted != null)
                    {
                        proposals.Add(ret.Accepted);
                    }
                    ++OKCount;
                }
            }

            if (OKCount <= _replica._view.Servers.Count / 2)
            {
                Console.WriteLine("Only get {0} promises among {1} acceptors.", OKCount, _replica._view.Servers.Count);
                return IssueResult.NoConsensus;
            }

            ConcreteProposal proposal = new ConcreteProposal()
            {
                Ballot = selfAppoint.Ballot,
                ID = selfAppoint.ID,
                DecreeGuid = _replica._id/*Guid.NewGuid()*/,
            };

            // select a proper command
            if (proposals.Count > 0)
            {          
                ConcreteProposal latest = proposals.Max();
                proposal.Command = latest.Command;

                // need to sync?
                if (latest.Ballot > selfAppoint.Ballot)
                {
                    throw new Exception("Should never arrive here");
                }
            }
            else
            {
                proposal.Command = command;
            }
            proposals.Clear();

            Console.WriteLine("Select proposal as {0}", proposal);

            // accept
            OKCount = 0;
            foreach (IPaxosAcceptor acceptor in _replica._acceptors)
            {
                AcceptResult ret;
                try
                {
                    ret = acceptor.Accept(proposal);
                    Console.WriteLine("Get accept-ret {0}", ret);

                    if (ret != null && ret.Promised.Ballot > _replica._localAcceptor._lastPromised.Ballot)
                    {
                        return IssueResult.OutOfDate;
                    }
                }
                catch (CommunicationObjectFaultedException e)
                {
                    _replica._needToCacheEndpoint = true;
                    ret = null;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Caught exception {0}", e.Message);
                    ret = null;
                }

                if (ret!=null && ret.Answer)
                {
                    ++OKCount;
                }
            }

            if (OKCount <= _replica._view.Servers.Count / 2)
            {
                Console.WriteLine("Only get {0} accepted among {1} acceptors.", OKCount, _replica._view.Servers.Count);
                return IssueResult.NoConsensus;
            }

            // commit
            Console.WriteLine("Dispatch proposal to commit {0}", proposal);
            foreach (IPaxosAcceptor acceptor in _replica._acceptors)
            {
                try
                {
                    acceptor.Commit(proposal);
                }
                catch (CommunicationObjectFaultedException e)
                {
                    _replica._needToCacheEndpoint = true;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Caught exception {0}", e.Message);
                    Console.WriteLine("One acceptor failed to commit proposal {0}.", proposal);
                }
            }

            return IssueResult.Success;
        }


        public PaxosCommand[] GetLatest(long startBallot)
        {
            return _replica._commandLogger.GetLatest(startBallot);
        }
        
        public void HitHeart()
        {
            Console.WriteLine("I'm alive!");
        }

        protected bool IsAlive(SGUID server)
        {
            IPaxosProproser node = _replica._proposerFactory.CreateChannel(new EndpointAddress(server.GetProposerEndpointAddress()));
            try
            {
                node.HitHeart();
            }
            catch
            {
                return false;
            }
            return true;
        }
    }
}
